fix(chat): enforce soft-delete checks and sanitize invisible characters. #40779#40859
fix(chat): enforce soft-delete checks and sanitize invisible characters. #40779#40859sam28u wants to merge 7 commits into
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
|
Walkthroughchat.update now rejects edits to soft-deleted messages and sanitizes update text by stripping Unicode control characters and trimming; chat.sendMessage applies the same sanitization, rejecting empty messages without attachments. ChangesMessage Input Validation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/meteor/app/api/server/v1/chat.ts (1)
271-276: ⚡ Quick winRemove inline implementation comments in these handlers.
Please drop the explanatory inline comments in the endpoint logic and keep the code self-descriptive.
As per coding guidelines,
**/*.{ts,tsx,js}files should “Avoid code comments in the implementation.”Also applies to: 856-867
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/meteor/app/api/server/v1/chat.ts` around lines 271 - 276, Remove the inline explanatory implementation comments inside the endpoint handler (e.g., the "Prevent editing of soft-deleted messages (Zombie Edits fix)" line and the "--- Fix : Strict Input Sanitization for updates ---" line near the logic referencing msg.t === 'rm'); instead, leave the code itself (the conditional checking msg.t and the failure return) as-is so it remains self-descriptive. Apply the same removal for the other commented block referenced (around lines 856-867) so all implementation-level explanatory comments in these handlers are deleted while keeping function names and behavior unchanged.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/meteor/app/api/server/v1/chat.ts`:
- Around line 278-285: Replace the NUL-only sanitization with a Unicode
control-character filter: update the usage of sanitizedText and the assignment
to bodyParams.text in chat handling to strip all invisible/control characters
(e.g. use a regex like /[\p{Cc}]/gu) then trim; e.g., compute sanitizedText =
bodyParams.text.replace(/[\p{Cc}]/gu, '').trim() and assign bodyParams.text =
sanitizedText (or the untrimmed but control-stripped string) so messages
composed only of control chars are rejected; apply the same change to the other
similar block referenced (the block around the 860-868 region) and keep
references to sanitizedText and bodyParams.text when making the edits.
---
Nitpick comments:
In `@apps/meteor/app/api/server/v1/chat.ts`:
- Around line 271-276: Remove the inline explanatory implementation comments
inside the endpoint handler (e.g., the "Prevent editing of soft-deleted messages
(Zombie Edits fix)" line and the "--- Fix : Strict Input Sanitization for
updates ---" line near the logic referencing msg.t === 'rm'); instead, leave the
code itself (the conditional checking msg.t and the failure return) as-is so it
remains self-descriptive. Apply the same removal for the other commented block
referenced (around lines 856-867) so all implementation-level explanatory
comments in these handlers are deleted while keeping function names and behavior
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 025bc968-1885-4f1e-9b87-8ddea3e52e92
📒 Files selected for processing (1)
apps/meteor/app/api/server/v1/chat.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
apps/meteor/app/api/server/v1/chat.ts
🧠 Learnings (5)
📚 Learning: 2026-02-23T17:53:06.802Z
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:06.802Z
Learning: During PR reviews that touch endpoint files under apps/meteor/app/api/server/v1, enforce strict scope: if a PR targets a specific endpoint (e.g., rooms.favorite), do not propose changes to unrelated endpoints (e.g., rooms.invite) unless maintainers explicitly request them. Focus feedback on the touched endpoint's behavior, API surface, and related tests; avoid broad cross-endpoint changes in the same PR unless requested.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-24T19:09:01.522Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:01.522Z
Learning: In Rocket.Chat OpenAPI migration PRs for endpoints under apps/meteor/app/api/server/v1, avoid introducing logic changes. Only perform scope-tight changes that preserve behavior; style-only cleanups (e.g., removing inline comments) may be deferred to follow-ups to keep the migration PR focused.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
There was a problem hiding this comment.
1 issue found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/meteor/app/api/server/v1/chat.ts">
<violation number="1" location="apps/meteor/app/api/server/v1/chat.ts:278">
P2: Sanitization only strips NUL (`\u0000`) but the error message and intent indicate guarding against all invisible control characters. A payload like `"\u0001"` will bypass this check and be stored in the database. Broaden the regex to cover the relevant C0 control character range (e.g., `[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]`) to match the stated security goal.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/meteor/app/api/server/v1/chat.ts (2)
276-285: 💤 Low valueConsider eliminating the duplicate regex replacement for efficiency and maintainability.
The same regex is applied twice: once for the emptiness check (line 278) and again for the mutation (line 284). Storing the stripped result in a single pass simplifies the code and ensures consistency.
♻️ Suggested refactor
// Sanitize invisible control characters (excluding safe whitespace) to prevent empty message bypass if ('text' in bodyParams && typeof bodyParams.text === 'string') { - const sanitizedText = bodyParams.text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '').trim(); + const strippedText = bodyParams.text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ''); - if (sanitizedText.length === 0) { + if (strippedText.trim().length === 0) { return API.v1.failure('Message cannot be empty or contain only invisible control characters.'); } - bodyParams.text = bodyParams.text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ''); + bodyParams.text = strippedText; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/meteor/app/api/server/v1/chat.ts` around lines 276 - 285, Compute the stripped string once and reuse it: replace both regex calls with a single variable (e.g., const stripped = bodyParams.text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '');), use stripped.trim().length to check emptiness and if valid assign bodyParams.text = stripped; update the block around the text handling in chat.ts to remove the duplicated regex and reference the single variable instead of calling replace twice.
856-867: 💤 Low valueSame optimization applies here: avoid duplicate regex replacement.
Consistent with the
chat.updatelogic, the regex is applied twice. A single-pass approach would be cleaner.♻️ Suggested refactor
// Sanitize invisible control characters (excluding safe whitespace) to prevent empty message bypass const msgPayload = this.bodyParams.message as { msg?: string; attachments?: any[] }; if (msgPayload && typeof msgPayload.msg === 'string') { - const sanitizedMsg = msgPayload.msg.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '').trim(); + const strippedMsg = msgPayload.msg.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ''); // Reject if the message is empty and has no attachments - if (sanitizedMsg.length === 0 && (!msgPayload.attachments || msgPayload.attachments.length === 0)) { + if (strippedMsg.trim().length === 0 && (!msgPayload.attachments || msgPayload.attachments.length === 0)) { return API.v1.failure('Message cannot be empty or contain only invisible control characters.'); } - msgPayload.msg = msgPayload.msg.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ''); + msgPayload.msg = strippedMsg; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/meteor/app/api/server/v1/chat.ts` around lines 856 - 867, Compute the sanitized string once and reuse it: for the msgPayload handling (symbols: msgPayload, sanitizedMsg, the control-char regex), replace the control characters a single time into a variable (e.g., sanitized = msgPayload.msg.replace(regex, '')), use sanitized.trim() to perform the empty check against attachments, and then assign msgPayload.msg = sanitized (instead of calling replace twice); this removes the duplicate regex pass while preserving the same validation and final message value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/meteor/app/api/server/v1/chat.ts`:
- Around line 276-285: Compute the stripped string once and reuse it: replace
both regex calls with a single variable (e.g., const stripped =
bodyParams.text.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '');), use
stripped.trim().length to check emptiness and if valid assign bodyParams.text =
stripped; update the block around the text handling in chat.ts to remove the
duplicated regex and reference the single variable instead of calling replace
twice.
- Around line 856-867: Compute the sanitized string once and reuse it: for the
msgPayload handling (symbols: msgPayload, sanitizedMsg, the control-char regex),
replace the control characters a single time into a variable (e.g., sanitized =
msgPayload.msg.replace(regex, '')), use sanitized.trim() to perform the empty
check against attachments, and then assign msgPayload.msg = sanitized (instead
of calling replace twice); this removes the duplicate regex pass while
preserving the same validation and final message value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 40ef5add-823f-4ecd-b9b7-324df49a9e23
📒 Files selected for processing (1)
apps/meteor/app/api/server/v1/chat.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
apps/meteor/app/api/server/v1/chat.ts
🧠 Learnings (5)
📚 Learning: 2026-02-23T17:53:06.802Z
Learnt from: ggazzo
Repo: RocketChat/Rocket.Chat PR: 35995
File: apps/meteor/app/api/server/v1/rooms.ts:1107-1112
Timestamp: 2026-02-23T17:53:06.802Z
Learning: During PR reviews that touch endpoint files under apps/meteor/app/api/server/v1, enforce strict scope: if a PR targets a specific endpoint (e.g., rooms.favorite), do not propose changes to unrelated endpoints (e.g., rooms.invite) unless maintainers explicitly request them. Focus feedback on the touched endpoint's behavior, API surface, and related tests; avoid broad cross-endpoint changes in the same PR unless requested.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-24T19:09:01.522Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:01.522Z
Learning: In Rocket.Chat OpenAPI migration PRs for endpoints under apps/meteor/app/api/server/v1, avoid introducing logic changes. Only perform scope-tight changes that preserve behavior; style-only cleanups (e.g., removing inline comments) may be deferred to follow-ups to keep the migration PR focused.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.
Applied to files:
apps/meteor/app/api/server/v1/chat.ts
Proposed changes
This PR patches two server-side validation bypasses in
chat.updateandchat.sendMessagethat allowed users to override UI restrictions via direct API calls.chat.updateto explicitly reject modifications if the message has a soft-delete status (msg.t === 'rm').chat.sendMessageandchat.updateto strip null bytes (\u0000) and trim payloads before evaluating string length.Issue(s)
Closes #40779
Steps to test or reproduce
Testing deleted messages:
msgId."t": "rm")./api/v1/chat.updatetargeting the deletedmsgIdwith new content.Cannot edit a deleted message.Testing null byte bypass:
/api/v1/chat.sendMessagewithmsg: "\u0000"./api/v1/chat.updateupdating an existing message to"\u0000".Message cannot be empty or contain only invisible control characters.Further comments
The null byte sanitization uses a standard regex strip (
.replace(/\u0000/g, '')) immediately before the length check. This ensures the database stays clean and prevents the markdown parser from rendering invisible components in the timeline.Summary by CodeRabbit